#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
GUI部署管理器
基于原有的deploy_kit.py开发的图形界面版本
支持项目管理、配置编辑、一键部署等功能
"""

import tkinter as tk
from tkinter import ttk, messagebox, filedialog
import json
import os
import threading
from typing import Dict, List, Any, Optional
from dataclasses import dataclass, asdict
from cryptography.fernet import Fernet
import base64
from datetime import datetime
import logging

# 导入原有的部署模块
from deploy_kit import DeploymentManager, SSHConnection, FileManager, Project

# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('logs/gui_deploy.log'),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger(__name__)


@dataclass
class ServerConfig:
    """服务器配置数据类"""
    hostname: str = ""
    port: int = 22
    username: str = ""
    password: str = ""
    auth_type: str = "密码"  # "密码" 或 "证书"
    key_path: str = ""


@dataclass
class DeployConfig:
    """部署配置数据类"""
    frontend_deploy_dir: str = ""
    frontend_deploy_script: str = ""
    backend_deploy_dir: str = ""
    backend_deploy_script: str = ""


@dataclass
class BuildConfig:
    """编译配置数据类"""
    java_dir: str = ""
    frontend_dir: str = ""
    backend_dir: str = ""
    frontend_commands: List[str] = None
    backend_commands: List[str] = None
    frontend_output_dir: str = "dist"  # 前端构建产物目录
    backend_output_pattern: str = "target/*.jar"  # 后端构建产物路径模式
    deploy_frontend: bool = True  # 是否部署前端
    deploy_backend: bool = True   # 是否部署后端

    def __post_init__(self):
        if self.frontend_commands is None:
            self.frontend_commands = ["pnpm install", "pnpm run build"]
        if self.backend_commands is None:
            self.backend_commands = ["mvn clean package -DskipTests"]


@dataclass
class ProjectConfig:
    """项目配置数据类"""
    name: str = ""
    enabled: bool = True
    server: ServerConfig = None
    deploy: DeployConfig = None
    build: BuildConfig = None
    created_at: str = ""
    updated_at: str = ""

    def __post_init__(self):
        if self.server is None:
            self.server = ServerConfig()
        if self.deploy is None:
            self.deploy = DeployConfig()
        if self.build is None:
            self.build = BuildConfig()
        if not self.created_at:
            self.created_at = datetime.now().isoformat()
        self.updated_at = datetime.now().isoformat()


class EncryptionManager:
    """加密管理器，用于加密存储敏感信息"""
    
    def __init__(self, key_file: str = "data/.key"):
        self.key_file = key_file
        self._ensure_key_exists()
        
    def _ensure_key_exists(self):
        """确保加密密钥存在"""
        os.makedirs(os.path.dirname(self.key_file), exist_ok=True)
        if not os.path.exists(self.key_file):
            key = Fernet.generate_key()
            with open(self.key_file, 'wb') as f:
                f.write(key)
    
    def _get_key(self) -> bytes:
        """获取加密密钥"""
        with open(self.key_file, 'rb') as f:
            return f.read()
    
    def encrypt(self, data: str) -> str:
        """加密字符串"""
        if not data:
            return ""
        fernet = Fernet(self._get_key())
        encrypted = fernet.encrypt(data.encode())
        return base64.b64encode(encrypted).decode()
    
    def decrypt(self, encrypted_data: str) -> str:
        """解密字符串"""
        if not encrypted_data:
            return ""
        try:
            fernet = Fernet(self._get_key())
            encrypted_bytes = base64.b64decode(encrypted_data.encode())
            decrypted = fernet.decrypt(encrypted_bytes)
            return decrypted.decode()
        except Exception:
            return encrypted_data  # 如果解密失败，返回原始数据


class DataManager:
    """数据管理器，负责项目配置的存储和读取"""
    
    def __init__(self, data_file: str = "data/projects.json"):
        self.data_file = data_file
        self.encryption = EncryptionManager()
        self._ensure_data_dir()
    
    def _ensure_data_dir(self):
        """确保数据目录存在"""
        os.makedirs(os.path.dirname(self.data_file), exist_ok=True)
    
    def save_projects(self, projects: List[ProjectConfig]):
        """保存项目配置"""
        data = []
        for project in projects:
            project_dict = asdict(project)
            # 加密敏感信息
            project_dict['server']['password'] = self.encryption.encrypt(project_dict['server']['password'])
            data.append(project_dict)
        
        with open(self.data_file, 'w', encoding='utf-8') as f:
            json.dump(data, f, ensure_ascii=False, indent=2)
    
    def load_projects(self) -> List[ProjectConfig]:
        """加载项目配置"""
        if not os.path.exists(self.data_file):
            return []
        
        try:
            with open(self.data_file, 'r', encoding='utf-8') as f:
                data = json.load(f)
            
            projects = []
            for item in data:
                # 解密敏感信息
                item['server']['password'] = self.encryption.decrypt(item['server']['password'])
                
                # 重构对象
                server = ServerConfig(**item['server'])
                deploy = DeployConfig(**item['deploy'])
                build = BuildConfig(**item['build'])
                project = ProjectConfig(
                    name=item['name'],
                    enabled=item['enabled'],
                    server=server,
                    deploy=deploy,
                    build=build,
                    created_at=item.get('created_at', ''),
                    updated_at=item.get('updated_at', '')
                )
                projects.append(project)
            
            return projects
        except Exception as e:
            logger.error(f"加载项目配置失败: {e}")
            return []


class ProjectCard(ttk.Frame):
    """项目卡片组件"""
    
    def __init__(self, parent, project: ProjectConfig, on_edit=None, on_deploy=None, on_delete=None):
        super().__init__(parent, relief='raised', borderwidth=1)
        self.project = project
        self.on_edit = on_edit
        self.on_deploy = on_deploy
        self.on_delete = on_delete
        
        self._create_widgets()
    
    def _create_widgets(self):
        """创建卡片组件"""
        # 主框架
        main_frame = ttk.Frame(self)
        main_frame.pack(fill='both', expand=True, padx=10, pady=10)
        
        # 标题行
        title_frame = ttk.Frame(main_frame)
        title_frame.pack(fill='x', pady=(0, 5))
        
        # 项目名称
        name_label = ttk.Label(title_frame, text=self.project.name, font=('Arial', 12, 'bold'))
        name_label.pack(side='left')
        
        # 启用状态
        status_text = "启用" if self.project.enabled else "禁用"
        status_color = "green" if self.project.enabled else "red"
        status_label = ttk.Label(title_frame, text=f"[{status_text}]", foreground=status_color)
        status_label.pack(side='right')
        
        # 信息行
        info_frame = ttk.Frame(main_frame)
        info_frame.pack(fill='x', pady=(0, 5))
        
        # 服务器信息
        server_info = f"服务器: {self.project.server.hostname}:{self.project.server.port}"
        ttk.Label(info_frame, text=server_info, font=('Arial', 9)).pack(anchor='w')
        
        # 认证方式
        auth_info = f"认证: {self.project.server.auth_type}"
        ttk.Label(info_frame, text=auth_info, font=('Arial', 9)).pack(anchor='w')
        
        # 部署选项
        deploy_options = []
        if self.project.build.deploy_frontend:
            deploy_options.append("前端")
        if self.project.build.deploy_backend:
            deploy_options.append("后端")
        if deploy_options:
            deploy_info = f"部署: {', '.join(deploy_options)}"
            ttk.Label(info_frame, text=deploy_info, font=('Arial', 9), foreground='blue').pack(anchor='w')
        
        # 更新时间
        if self.project.updated_at:
            try:
                update_time = datetime.fromisoformat(self.project.updated_at)
                time_str = update_time.strftime("%Y-%m-%d %H:%M")
                ttk.Label(info_frame, text=f"更新: {time_str}", font=('Arial', 8), foreground='gray').pack(anchor='w')
            except:
                pass
        
        # 按钮行
        button_frame = ttk.Frame(main_frame)
        button_frame.pack(fill='x', pady=(5, 0))
        
        # 编辑按钮
        ttk.Button(button_frame, text="编辑", command=self._on_edit_click).pack(side='left', padx=(0, 5))
        
        # 部署按钮
        deploy_btn = ttk.Button(button_frame, text="部署", command=self._on_deploy_click)
        deploy_btn.pack(side='left', padx=(0, 5))
        if not self.project.enabled:
            deploy_btn.configure(state='disabled')
        
        # 删除按钮
        ttk.Button(button_frame, text="删除", command=self._on_delete_click).pack(side='right')
    
    def _on_edit_click(self):
        if self.on_edit:
            self.on_edit(self.project)
    
    def _on_deploy_click(self):
        if self.on_deploy:
            self.on_deploy(self.project)
    
    def _on_delete_click(self):
        if self.on_delete:
            self.on_delete(self.project)


class ProjectEditDialog:
    """项目编辑对话框"""
    
    def __init__(self, parent, project: ProjectConfig = None):
        self.parent = parent
        self.project = project or ProjectConfig()
        self.result = None
        
        self._create_dialog()
    
    def _create_dialog(self):
        """创建对话框"""
        self.dialog = tk.Toplevel(self.parent)
        self.dialog.title("编辑项目" if self.project.name else "新建项目")
        self.dialog.geometry("600x700")
        self.dialog.resizable(True, True)
        self.dialog.transient(self.parent)
        self.dialog.grab_set()
        
        # 居中显示
        self.dialog.update_idletasks()
        x = (self.dialog.winfo_screenwidth() // 2) - (600 // 2)
        y = (self.dialog.winfo_screenheight() // 2) - (700 // 2)
        self.dialog.geometry(f"600x700+{x}+{y}")
        
        self._create_widgets()
        self._load_data()
    
    def _create_widgets(self):
        """创建对话框组件"""
        # 主框架
        main_frame = ttk.Frame(self.dialog)
        main_frame.pack(fill='both', expand=True, padx=20, pady=20)
        
        # 创建笔记本控件（标签页）
        notebook = ttk.Notebook(main_frame)
        notebook.pack(fill='both', expand=True, pady=(0, 10))
        
        # 基本信息标签页
        self._create_basic_tab(notebook)
        
        # 服务器信息标签页
        self._create_server_tab(notebook)
        
        # 部署信息标签页
        self._create_deploy_tab(notebook)
        
        # 编译信息标签页
        self._create_build_tab(notebook)
        
        # 按钮框架
        button_frame = ttk.Frame(main_frame)
        button_frame.pack(fill='x')
        
        ttk.Button(button_frame, text="保存", command=self._save).pack(side='right', padx=(5, 0))
        ttk.Button(button_frame, text="取消", command=self._cancel).pack(side='right')
        ttk.Button(button_frame, text="测试连接", command=self._test_connection).pack(side='left')
    
    def _create_basic_tab(self, notebook):
        """创建基本信息标签页"""
        frame = ttk.Frame(notebook)
        notebook.add(frame, text="基本信息")
        
        # 项目名称
        ttk.Label(frame, text="项目名称:").grid(row=0, column=0, sticky='w', padx=5, pady=5)
        self.name_var = tk.StringVar()
        ttk.Entry(frame, textvariable=self.name_var, width=40).grid(row=0, column=1, sticky='ew', padx=5, pady=5)
        
        # 启用状态
        self.enabled_var = tk.BooleanVar()
        ttk.Checkbutton(frame, text="启用项目", variable=self.enabled_var).grid(row=1, column=0, columnspan=2, sticky='w', padx=5, pady=5)
        
        frame.columnconfigure(1, weight=1)
    
    def _create_server_tab(self, notebook):
        """创建服务器信息标签页"""
        frame = ttk.Frame(notebook)
        notebook.add(frame, text="服务器信息")
        
        # IP地址
        ttk.Label(frame, text="IP地址:").grid(row=0, column=0, sticky='w', padx=5, pady=5)
        self.hostname_var = tk.StringVar()
        ttk.Entry(frame, textvariable=self.hostname_var, width=40).grid(row=0, column=1, sticky='ew', padx=5, pady=5)
        
        # 端口
        ttk.Label(frame, text="端口:").grid(row=1, column=0, sticky='w', padx=5, pady=5)
        self.port_var = tk.StringVar()
        ttk.Entry(frame, textvariable=self.port_var, width=40).grid(row=1, column=1, sticky='ew', padx=5, pady=5)
        
        # 用户名
        ttk.Label(frame, text="用户名:").grid(row=2, column=0, sticky='w', padx=5, pady=5)
        self.username_var = tk.StringVar()
        ttk.Entry(frame, textvariable=self.username_var, width=40).grid(row=2, column=1, sticky='ew', padx=5, pady=5)
        
        # 认证方式
        ttk.Label(frame, text="认证方式:").grid(row=3, column=0, sticky='w', padx=5, pady=5)
        self.auth_type_var = tk.StringVar()
        auth_combo = ttk.Combobox(frame, textvariable=self.auth_type_var, values=["密码", "证书"], state="readonly", width=37)
        auth_combo.grid(row=3, column=1, sticky='ew', padx=5, pady=5)
        auth_combo.bind('<<ComboboxSelected>>', self._on_auth_type_change)
        
        # 密码
        ttk.Label(frame, text="密码:").grid(row=4, column=0, sticky='w', padx=5, pady=5)
        self.password_var = tk.StringVar()
        self.password_entry = ttk.Entry(frame, textvariable=self.password_var, show="*", width=40)
        self.password_entry.grid(row=4, column=1, sticky='ew', padx=5, pady=5)
        
        # 证书路径
        ttk.Label(frame, text="证书路径:").grid(row=5, column=0, sticky='w', padx=5, pady=5)
        key_frame = ttk.Frame(frame)
        key_frame.grid(row=5, column=1, sticky='ew', padx=5, pady=5)
        self.key_path_var = tk.StringVar()
        self.key_path_entry = ttk.Entry(key_frame, textvariable=self.key_path_var)
        self.key_path_entry.pack(side='left', fill='x', expand=True)
        ttk.Button(key_frame, text="浏览", command=self._browse_key_file).pack(side='right', padx=(5, 0))
        
        frame.columnconfigure(1, weight=1)
    
    def _create_deploy_tab(self, notebook):
        """创建部署信息标签页"""
        frame = ttk.Frame(notebook)
        notebook.add(frame, text="部署信息")
        
        # 前端部署目录
        ttk.Label(frame, text="前端部署目录:").grid(row=0, column=0, sticky='w', padx=5, pady=5)
        self.frontend_deploy_dir_var = tk.StringVar()
        ttk.Entry(frame, textvariable=self.frontend_deploy_dir_var, width=40).grid(row=0, column=1, sticky='ew', padx=5, pady=5)
        
        # 前端部署脚本
        ttk.Label(frame, text="前端部署脚本:").grid(row=1, column=0, sticky='w', padx=5, pady=5)
        self.frontend_deploy_script_var = tk.StringVar()
        ttk.Entry(frame, textvariable=self.frontend_deploy_script_var, width=40).grid(row=1, column=1, sticky='ew', padx=5, pady=5)
        
        # 后端部署目录
        ttk.Label(frame, text="后端部署目录:").grid(row=2, column=0, sticky='w', padx=5, pady=5)
        self.backend_deploy_dir_var = tk.StringVar()
        ttk.Entry(frame, textvariable=self.backend_deploy_dir_var, width=40).grid(row=2, column=1, sticky='ew', padx=5, pady=5)
        
        # 后端部署脚本
        ttk.Label(frame, text="后端部署脚本:").grid(row=3, column=0, sticky='w', padx=5, pady=5)
        self.backend_deploy_script_var = tk.StringVar()
        ttk.Entry(frame, textvariable=self.backend_deploy_script_var, width=40).grid(row=3, column=1, sticky='ew', padx=5, pady=5)
        
        frame.columnconfigure(1, weight=1)
    
    def _create_build_tab(self, notebook):
        """创建编译信息标签页"""
        frame = ttk.Frame(notebook)
        notebook.add(frame, text="编译信息")
        
        # Java目录
        ttk.Label(frame, text="Java目录:").grid(row=0, column=0, sticky='w', padx=5, pady=5)
        java_frame = ttk.Frame(frame)
        java_frame.grid(row=0, column=1, sticky='ew', padx=5, pady=5)
        self.java_dir_var = tk.StringVar()
        ttk.Entry(java_frame, textvariable=self.java_dir_var).pack(side='left', fill='x', expand=True)
        ttk.Button(java_frame, text="浏览", command=lambda: self._browse_directory(self.java_dir_var)).pack(side='right', padx=(5, 0))
        
        # 前端目录
        ttk.Label(frame, text="前端目录:").grid(row=1, column=0, sticky='w', padx=5, pady=5)
        frontend_frame = ttk.Frame(frame)
        frontend_frame.grid(row=1, column=1, sticky='ew', padx=5, pady=5)
        self.frontend_dir_var = tk.StringVar()
        ttk.Entry(frontend_frame, textvariable=self.frontend_dir_var).pack(side='left', fill='x', expand=True)
        ttk.Button(frontend_frame, text="浏览", command=lambda: self._browse_directory(self.frontend_dir_var)).pack(side='right', padx=(5, 0))
        
        # 后端目录
        ttk.Label(frame, text="后端目录:").grid(row=2, column=0, sticky='w', padx=5, pady=5)
        backend_frame = ttk.Frame(frame)
        backend_frame.grid(row=2, column=1, sticky='ew', padx=5, pady=5)
        self.backend_dir_var = tk.StringVar()
        backend_entry = ttk.Entry(backend_frame, textvariable=self.backend_dir_var)
        backend_entry.pack(side='left', fill='x', expand=True)
        backend_entry.bind('<FocusOut>', self._on_backend_dir_change)
        ttk.Button(backend_frame, text="浏览", command=self._browse_backend_directory).pack(side='right', padx=(5, 0))
        
        # 前端编译命令
        ttk.Label(frame, text="前端编译命令:").grid(row=3, column=0, sticky='nw', padx=5, pady=5)
        self.frontend_commands_text = tk.Text(frame, height=3, width=40)
        self.frontend_commands_text.grid(row=3, column=1, sticky='ew', padx=5, pady=5)
        
        # 前端输出目录
        ttk.Label(frame, text="前端输出目录:").grid(row=4, column=0, sticky='w', padx=5, pady=5)
        self.frontend_output_dir_var = tk.StringVar()
        ttk.Entry(frame, textvariable=self.frontend_output_dir_var, width=40).grid(row=4, column=1, sticky='ew', padx=5, pady=5)
        
        # 后端编译命令
        ttk.Label(frame, text="后端编译命令:").grid(row=5, column=0, sticky='nw', padx=5, pady=5)
        self.backend_commands_text = tk.Text(frame, height=3, width=40)
        self.backend_commands_text.grid(row=5, column=1, sticky='ew', padx=5, pady=5)
        
        # 后端输出路径模式
        ttk.Label(frame, text="后端输出路径:").grid(row=6, column=0, sticky='w', padx=5, pady=5)
        self.backend_output_pattern_var = tk.StringVar()
        ttk.Entry(frame, textvariable=self.backend_output_pattern_var, width=40).grid(row=6, column=1, sticky='ew', padx=5, pady=5)
        
        # 部署选项
        ttk.Label(frame, text="部署选项:").grid(row=7, column=0, sticky='nw', padx=5, pady=5)
        deploy_options_frame = ttk.Frame(frame)
        deploy_options_frame.grid(row=7, column=1, sticky='ew', padx=5, pady=5)
        
        self.deploy_frontend_var = tk.BooleanVar(value=True)
        ttk.Checkbutton(deploy_options_frame, text="部署前端", variable=self.deploy_frontend_var).pack(anchor='w')
        
        self.deploy_backend_var = tk.BooleanVar(value=True)
        ttk.Checkbutton(deploy_options_frame, text="部署后端", variable=self.deploy_backend_var).pack(anchor='w')
        
        frame.columnconfigure(1, weight=1)
    
    def _on_auth_type_change(self, event=None):
        """认证方式改变时的处理"""
        auth_type = self.auth_type_var.get()
        if auth_type == "密码":
            self.password_entry.configure(state='normal')
            self.key_path_entry.configure(state='disabled')
        else:
            self.password_entry.configure(state='disabled')
            self.key_path_entry.configure(state='normal')
    
    def _browse_key_file(self):
        """浏览证书文件"""
        filename = filedialog.askopenfilename(
            title="选择证书文件",
            filetypes=[("PEM files", "*.pem"), ("All files", "*.*")]
        )
        if filename:
            self.key_path_var.set(filename)
    
    def _browse_directory(self, var):
        """浏览目录"""
        directory = filedialog.askdirectory(title="选择目录")
        if directory:
            var.set(directory)
    
    def _browse_backend_directory(self):
        """浏览后端目录并自动生成输出路径"""
        directory = filedialog.askdirectory(title="选择后端目录")
        if directory:
            self.backend_dir_var.set(directory)
            self._update_backend_output_pattern(directory)
    
    def _on_backend_dir_change(self, event=None):
        """后端目录变更时的处理"""
        directory = self.backend_dir_var.get().strip()
        if directory:
            self._update_backend_output_pattern(directory)
    
    def _update_backend_output_pattern(self, directory):
        """根据后端目录自动更新输出路径模式"""
        if not directory or not directory.strip():
            return
        
        # 获取目录的最后一部分作为项目名
        import os
        cleaned_path = directory.strip().rstrip('/\\')
        if not cleaned_path or cleaned_path in ['/', 'C:\\', 'D:\\', 'E:\\']:
            return
        
        project_name = os.path.basename(cleaned_path)
        
        if project_name and project_name.strip():
            # 生成输出路径模式：target/项目名.jar (统一使用Unix格式的正斜杠)
            output_pattern = f"target/{project_name.strip()}.jar"
            self.backend_output_pattern_var.set(output_pattern)
    
    def _load_data(self):
        """加载项目数据到表单"""
        self.name_var.set(self.project.name)
        self.enabled_var.set(self.project.enabled)
        
        # 服务器信息
        self.hostname_var.set(self.project.server.hostname)
        self.port_var.set(str(self.project.server.port))
        self.username_var.set(self.project.server.username)
        self.password_var.set(self.project.server.password)
        self.auth_type_var.set(self.project.server.auth_type)
        self.key_path_var.set(self.project.server.key_path)
        
        # 部署信息
        self.frontend_deploy_dir_var.set(self.project.deploy.frontend_deploy_dir)
        self.frontend_deploy_script_var.set(self.project.deploy.frontend_deploy_script)
        self.backend_deploy_dir_var.set(self.project.deploy.backend_deploy_dir)
        self.backend_deploy_script_var.set(self.project.deploy.backend_deploy_script)
        
        # 编译信息
        self.java_dir_var.set(self.project.build.java_dir)
        self.frontend_dir_var.set(self.project.build.frontend_dir)
        self.backend_dir_var.set(self.project.build.backend_dir)
        
        # 编译命令
        self.frontend_commands_text.delete('1.0', tk.END)
        self.frontend_commands_text.insert('1.0', '\n'.join(self.project.build.frontend_commands))
        
        self.backend_commands_text.delete('1.0', tk.END)
        self.backend_commands_text.insert('1.0', '\n'.join(self.project.build.backend_commands))
        
        # 输出路径配置
        self.frontend_output_dir_var.set(self.project.build.frontend_output_dir)
        self.backend_output_pattern_var.set(self.project.build.backend_output_pattern)
        
        # 部署选项
        self.deploy_frontend_var.set(self.project.build.deploy_frontend)
        self.deploy_backend_var.set(self.project.build.deploy_backend)
        
        # 触发认证方式变更
        self._on_auth_type_change()
    
    def _test_connection(self):
        """测试服务器连接"""
        try:
            # 获取当前表单数据
            hostname = self.hostname_var.get().strip()
            port = int(self.port_var.get().strip() or "22")
            username = self.username_var.get().strip()
            password = self.password_var.get()
            auth_type = self.auth_type_var.get()
            key_path = self.key_path_var.get().strip()
            
            if not hostname or not username:
                messagebox.showerror("错误", "请填写服务器地址和用户名")
                return
            
            if auth_type == "密码" and not password:
                messagebox.showerror("错误", "请填写密码")
                return
            
            if auth_type == "证书" and not key_path:
                messagebox.showerror("错误", "请选择证书文件")
                return
            
            # 创建连接测试
            def test_in_thread():
                try:
                    with SSHConnection(
                        hostname=hostname,
                        port=port,
                        username=username,
                        auth_type=auth_type,
                        password=password if auth_type == "密码" else None,
                        key_path=key_path if auth_type == "证书" else None
                    ) as conn:
                        stdout, stderr = conn.execute_command("echo 'Connection test successful'")
                        self.dialog.after(0, lambda: messagebox.showinfo("成功", "服务器连接测试成功！"))
                except Exception as e:
                    self.dialog.after(0, lambda: messagebox.showerror("连接失败", f"无法连接到服务器：{str(e)}"))
            
            threading.Thread(target=test_in_thread, daemon=True).start()
            
        except ValueError:
            messagebox.showerror("错误", "端口号必须是数字")
        except Exception as e:
            messagebox.showerror("错误", f"测试连接时发生错误：{str(e)}")
    
    def _save(self):
        """保存项目配置"""
        try:
            # 验证必填字段
            if not self.name_var.get().strip():
                messagebox.showerror("错误", "请填写项目名称")
                return
            
            # 更新项目配置
            self.project.name = self.name_var.get().strip()
            self.project.enabled = self.enabled_var.get()
            
            # 服务器信息
            self.project.server.hostname = self.hostname_var.get().strip()
            try:
                self.project.server.port = int(self.port_var.get().strip() or "22")
            except ValueError:
                messagebox.showerror("错误", "端口号必须是数字")
                return
            
            self.project.server.username = self.username_var.get().strip()
            self.project.server.password = self.password_var.get()
            self.project.server.auth_type = self.auth_type_var.get()
            self.project.server.key_path = self.key_path_var.get().strip()
            
            # 部署信息
            self.project.deploy.frontend_deploy_dir = self.frontend_deploy_dir_var.get().strip()
            self.project.deploy.frontend_deploy_script = self.frontend_deploy_script_var.get().strip()
            self.project.deploy.backend_deploy_dir = self.backend_deploy_dir_var.get().strip()
            self.project.deploy.backend_deploy_script = self.backend_deploy_script_var.get().strip()
            
            # 编译信息
            self.project.build.java_dir = self.java_dir_var.get().strip()
            self.project.build.frontend_dir = self.frontend_dir_var.get().strip()
            self.project.build.backend_dir = self.backend_dir_var.get().strip()
            
            # 编译命令
            frontend_commands = self.frontend_commands_text.get('1.0', tk.END).strip()
            self.project.build.frontend_commands = [cmd.strip() for cmd in frontend_commands.split('\n') if cmd.strip()]
            
            backend_commands = self.backend_commands_text.get('1.0', tk.END).strip()
            self.project.build.backend_commands = [cmd.strip() for cmd in backend_commands.split('\n') if cmd.strip()]
            
            # 输出路径配置
            self.project.build.frontend_output_dir = self.frontend_output_dir_var.get().strip() or "dist"
            self.project.build.backend_output_pattern = self.backend_output_pattern_var.get().strip() or "target/*.jar"
            
            # 部署选项
            self.project.build.deploy_frontend = self.deploy_frontend_var.get()
            self.project.build.deploy_backend = self.deploy_backend_var.get()
            
            # 更新时间
            self.project.updated_at = datetime.now().isoformat()
            
            self.result = self.project
            self.dialog.destroy()
            
        except Exception as e:
            messagebox.showerror("错误", f"保存配置时发生错误：{str(e)}")
    
    def _cancel(self):
        """取消编辑"""
        self.dialog.destroy()
    
    def show(self) -> Optional[ProjectConfig]:
        """显示对话框并返回结果"""
        self.dialog.wait_window()
        return self.result


class DeployProgressDialog:
    """部署进度对话框"""
    
    def __init__(self, parent, project: ProjectConfig):
        self.parent = parent
        self.project = project
        self.dialog = None
        self.progress_var = None
        self.status_var = None
        self.log_text = None
        self.is_cancelled = False
        
        self._create_dialog()
    
    def _create_dialog(self):
        """创建进度对话框"""
        self.dialog = tk.Toplevel(self.parent)
        self.dialog.title(f"部署项目: {self.project.name}")
        self.dialog.geometry("600x400")
        self.dialog.resizable(True, True)
        self.dialog.transient(self.parent)
        self.dialog.grab_set()
        
        # 居中显示
        self.dialog.update_idletasks()
        x = (self.dialog.winfo_screenwidth() // 2) - (600 // 2)
        y = (self.dialog.winfo_screenheight() // 2) - (400 // 2)
        self.dialog.geometry(f"600x400+{x}+{y}")
        
        # 主框架
        main_frame = ttk.Frame(self.dialog)
        main_frame.pack(fill='both', expand=True, padx=20, pady=20)
        
        # 状态标签
        self.status_var = tk.StringVar(value="准备开始部署...")
        ttk.Label(main_frame, textvariable=self.status_var, font=('Arial', 10)).pack(anchor='w', pady=(0, 10))
        
        # 进度条
        self.progress_var = tk.DoubleVar()
        progress_bar = ttk.Progressbar(main_frame, variable=self.progress_var, maximum=100)
        progress_bar.pack(fill='x', pady=(0, 10))
        
        # 日志文本框
        log_frame = ttk.LabelFrame(main_frame, text="部署日志")
        log_frame.pack(fill='both', expand=True, pady=(0, 10))
        
        # 创建文本框和滚动条
        text_frame = ttk.Frame(log_frame)
        text_frame.pack(fill='both', expand=True, padx=5, pady=5)
        
        self.log_text = tk.Text(text_frame, wrap=tk.WORD, state='disabled')
        scrollbar = ttk.Scrollbar(text_frame, orient='vertical', command=self.log_text.yview)
        self.log_text.configure(yscrollcommand=scrollbar.set)
        
        self.log_text.pack(side='left', fill='both', expand=True)
        scrollbar.pack(side='right', fill='y')
        
        # 按钮框架
        button_frame = ttk.Frame(main_frame)
        button_frame.pack(fill='x')
        
        self.cancel_button = ttk.Button(button_frame, text="取消", command=self._cancel)
        self.cancel_button.pack(side='right')
        
        self.close_button = ttk.Button(button_frame, text="关闭", command=self._close, state='disabled')
        self.close_button.pack(side='right', padx=(0, 5))
    
    def update_progress(self, value: float, status: str = None):
        """更新进度"""
        self.progress_var.set(value)
        if status:
            self.status_var.set(status)
    
    def add_log(self, message: str):
        """添加日志消息"""
        self.log_text.configure(state='normal')
        self.log_text.insert(tk.END, f"[{datetime.now().strftime('%H:%M:%S')}] {message}\n")
        self.log_text.configure(state='disabled')
        self.log_text.see(tk.END)
    
    def deployment_finished(self, success: bool):
        """部署完成"""
        if success:
            self.update_progress(100, "部署完成")
            self.add_log("部署成功完成！")
        else:
            self.update_progress(100, "部署失败")
            self.add_log("部署失败，请检查日志信息。")
        
        self.cancel_button.configure(state='disabled')
        self.close_button.configure(state='normal')
    
    def _cancel(self):
        """取消部署"""
        self.is_cancelled = True
        self.add_log("用户取消部署操作")
        self.deployment_finished(False)
    
    def _close(self):
        """关闭对话框"""
        self.dialog.destroy()
    
    def show(self):
        """显示对话框"""
        return self.dialog


class MainApplication:
    """主应用程序"""
    
    def __init__(self):
        self.root = tk.Tk()
        self.root.title("部署管理器 v1.0")
        self.root.geometry("1000x700")
        
        # 数据管理器
        self.data_manager = DataManager()
        self.projects = self.data_manager.load_projects()
        
        self._create_widgets()
        self._refresh_project_list()
    
    def _create_widgets(self):
        """创建主界面组件"""
        # 主框架
        main_frame = ttk.Frame(self.root)
        main_frame.pack(fill='both', expand=True, padx=10, pady=10)
        
        # 工具栏
        toolbar = ttk.Frame(main_frame)
        toolbar.pack(fill='x', pady=(0, 10))
        
        ttk.Button(toolbar, text="新建项目", command=self._new_project).pack(side='left', padx=(0, 5))
        ttk.Button(toolbar, text="刷新", command=self._refresh_project_list).pack(side='left', padx=(0, 5))
        ttk.Button(toolbar, text="全部部署", command=self._deploy_all).pack(side='left', padx=(0, 5))
        
        # 搜索框
        search_frame = ttk.Frame(toolbar)
        search_frame.pack(side='right')
        ttk.Label(search_frame, text="搜索:").pack(side='left')
        self.search_var = tk.StringVar()
        self.search_var.trace('w', self._on_search_change)
        ttk.Entry(search_frame, textvariable=self.search_var, width=20).pack(side='left', padx=(5, 0))
        
        # 项目列表框架
        list_frame = ttk.LabelFrame(main_frame, text="项目列表")
        list_frame.pack(fill='both', expand=True)
        
        # 创建滚动区域
        canvas = tk.Canvas(list_frame)
        scrollbar = ttk.Scrollbar(list_frame, orient="vertical", command=canvas.yview)
        self.scrollable_frame = ttk.Frame(canvas)
        
        self.scrollable_frame.bind(
            "<Configure>",
            lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
        )
        
        canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
        canvas.configure(yscrollcommand=scrollbar.set)
        
        canvas.pack(side="left", fill="both", expand=True, padx=5, pady=5)
        scrollbar.pack(side="right", fill="y", pady=5)
        
        # 绑定鼠标滚轮
        def _on_mousewheel(event):
            canvas.yview_scroll(int(-1*(event.delta/120)), "units")
        canvas.bind("<MouseWheel>", _on_mousewheel)
        
        # 状态栏
        self.status_var = tk.StringVar(value="就绪")
        status_bar = ttk.Label(main_frame, textvariable=self.status_var, relief='sunken')
        status_bar.pack(fill='x', pady=(10, 0))
    
    def _refresh_project_list(self):
        """刷新项目列表"""
        # 清空现有组件
        for widget in self.scrollable_frame.winfo_children():
            widget.destroy()
        
        # 获取搜索关键词
        search_term = self.search_var.get().lower()
        
        # 过滤项目
        filtered_projects = []
        for project in self.projects:
            if not search_term or search_term in project.name.lower():
                filtered_projects.append(project)
        
        # 创建项目卡片
        if filtered_projects:
            for i, project in enumerate(filtered_projects):
                card = ProjectCard(
                    self.scrollable_frame,
                    project,
                    on_edit=self._edit_project,
                    on_deploy=self._deploy_project,
                    on_delete=self._delete_project
                )
                card.pack(fill='x', padx=5, pady=5)
        else:
            # 显示空状态
            empty_label = ttk.Label(
                self.scrollable_frame,
                text="没有找到项目" if search_term else "暂无项目，点击'新建项目'开始",
                font=('Arial', 12),
                foreground='gray'
            )
            empty_label.pack(expand=True, pady=50)
        
        # 更新状态栏
        total_count = len(self.projects)
        enabled_count = len([p for p in self.projects if p.enabled])
        self.status_var.set(f"总计 {total_count} 个项目，{enabled_count} 个已启用")
    
    def _on_search_change(self, *args):
        """搜索内容变化时的处理"""
        self._refresh_project_list()
    
    def _new_project(self):
        """新建项目"""
        dialog = ProjectEditDialog(self.root)
        result = dialog.show()
        if result:
            # 检查项目名称是否重复
            if any(p.name == result.name for p in self.projects):
                messagebox.showerror("错误", "项目名称已存在，请使用其他名称")
                return
            
            self.projects.append(result)
            self._save_projects()
            self._refresh_project_list()
            messagebox.showinfo("成功", "项目创建成功")
    
    def _edit_project(self, project: ProjectConfig):
        """编辑项目"""
        dialog = ProjectEditDialog(self.root, project)
        result = dialog.show()
        if result:
            # 检查项目名称是否与其他项目重复
            if any(p.name == result.name and p != project for p in self.projects):
                messagebox.showerror("错误", "项目名称已存在，请使用其他名称")
                return
            
            # 更新项目
            index = self.projects.index(project)
            self.projects[index] = result
            self._save_projects()
            self._refresh_project_list()
            messagebox.showinfo("成功", "项目更新成功")
    
    def _delete_project(self, project: ProjectConfig):
        """删除项目"""
        if messagebox.askyesno("确认删除", f"确定要删除项目 '{project.name}' 吗？\n此操作不可撤销。"):
            self.projects.remove(project)
            self._save_projects()
            self._refresh_project_list()
            messagebox.showinfo("成功", "项目删除成功")
    
    def _deploy_project(self, project: ProjectConfig):
        """部署单个项目"""
        if not project.enabled:
            messagebox.showwarning("警告", "项目未启用，无法部署")
            return
        
        # 创建进度对话框
        progress_dialog = DeployProgressDialog(self.root, project)
        dialog_window = progress_dialog.show()
        
        def deploy_in_thread():
            try:
                progress_dialog.add_log(f"开始部署项目: {project.name}")
                progress_dialog.update_progress(10, "准备部署环境...")
                
                # 转换为原有格式的配置
                config = self._convert_to_deploy_config(project)
                
                progress_dialog.add_log("创建部署管理器...")
                progress_dialog.update_progress(20, "连接服务器...")
                
                # 创建临时配置文件
                import tempfile
                import yaml
                
                with tempfile.NamedTemporaryFile(mode='w', suffix='.yaml', delete=False, encoding='utf-8') as f:
                    yaml.dump(config, f, default_flow_style=False, allow_unicode=True)
                    temp_config_file = f.name
                
                try:
                    with DeploymentManager(temp_config_file) as manager:
                        progress_dialog.add_log("服务器连接成功")
                        progress_dialog.update_progress(30, "开始构建项目...")
                        
                        # 执行部署
                        for i, proj in enumerate(manager.projects):
                            if progress_dialog.is_cancelled:
                                break
                            
                            progress = 30 + (i + 1) * 60 / len(manager.projects)
                            progress_dialog.update_progress(progress, f"部署 {proj.project_name}...")
                            progress_dialog.add_log(f"开始部署 {proj.project_name}")
                            
                            proj.build()
                            progress_dialog.add_log(f"{proj.project_name} 构建完成")
                            
                            proj.deploy()
                            progress_dialog.add_log(f"{proj.project_name} 部署完成")
                        
                        if not progress_dialog.is_cancelled:
                            progress_dialog.deployment_finished(True)
                        
                finally:
                    # 清理临时文件
                    try:
                        os.unlink(temp_config_file)
                    except:
                        pass
                
            except Exception as e:
                progress_dialog.add_log(f"部署失败: {str(e)}")
                progress_dialog.deployment_finished(False)
                logger.error(f"部署项目 {project.name} 失败: {e}")
        
        # 在后台线程中执行部署
        threading.Thread(target=deploy_in_thread, daemon=True).start()
    
    def _deploy_all(self):
        """部署所有启用的项目"""
        enabled_projects = [p for p in self.projects if p.enabled]
        if not enabled_projects:
            messagebox.showwarning("警告", "没有启用的项目可以部署")
            return
        
        if not messagebox.askyesno("确认部署", f"确定要部署所有 {len(enabled_projects)} 个启用的项目吗？"):
            return
        
        # TODO: 实现批量部署功能
        messagebox.showinfo("提示", "批量部署功能正在开发中，请逐个部署项目")
    
    def _convert_to_deploy_config(self, project: ProjectConfig) -> Dict[str, Any]:
        """将项目配置转换为原有部署系统的配置格式"""
        config = {
            'server': {
                'hostname': project.server.hostname,
                'port': project.server.port,
                'username': project.server.username,
                'auth_type': project.server.auth_type
            },
            'projects': []
        }
        
        # 添加认证信息
        if project.server.auth_type == "密码":
            config['server']['password'] = project.server.password
        else:
            config['server']['key_path'] = project.server.key_path
        
        # 添加前端项目（如果配置了且选择部署）
        if (project.build.deploy_frontend and 
            project.build.frontend_dir and 
            project.deploy.frontend_deploy_dir):
            frontend_project = {
                'name': f"{project.name}-frontend",
                'enabled': True,
                'local_dir': project.build.frontend_dir,
                'commands': project.build.frontend_commands,
                'remote_dir': project.deploy.frontend_deploy_dir,
                'artifacts': {
                    'compress': True,
                    'source_dir': project.build.frontend_output_dir,
                    'output_file': f'{project.build.frontend_output_dir}.zip'
                }
            }
            
            if project.deploy.frontend_deploy_script:
                frontend_project['deploy_script'] = project.deploy.frontend_deploy_script
            
            config['projects'].append(frontend_project)
        
        # 添加后端项目（如果配置了且选择部署）
        if (project.build.deploy_backend and 
            project.build.backend_dir and 
            project.deploy.backend_deploy_dir):
            backend_project = {
                'name': f"{project.name}-backend",
                'enabled': True,
                'local_dir': project.build.backend_dir,
                'commands': project.build.backend_commands,
                'remote_dir': project.deploy.backend_deploy_dir,
                'artifacts': {
                    'output_file': project.build.backend_output_pattern
                }
            }
            
            if project.deploy.backend_deploy_script:
                backend_project['deploy_script'] = project.deploy.backend_deploy_script
            
            config['projects'].append(backend_project)
        
        return config
    
    def _save_projects(self):
        """保存项目配置"""
        try:
            self.data_manager.save_projects(self.projects)
        except Exception as e:
            messagebox.showerror("错误", f"保存项目配置失败：{str(e)}")
            logger.error(f"保存项目配置失败: {e}")
    
    def run(self):
        """运行应用程序"""
        # 设置窗口图标（如果有的话）
        try:
            # self.root.iconbitmap('icon.ico')
            pass
        except:
            pass
        
        # 设置关闭事件
        self.root.protocol("WM_DELETE_WINDOW", self._on_closing)
        
        # 居中显示窗口
        self.root.update_idletasks()
        x = (self.root.winfo_screenwidth() // 2) - (1000 // 2)
        y = (self.root.winfo_screenheight() // 2) - (700 // 2)
        self.root.geometry(f"1000x700+{x}+{y}")
        
        # 启动主循环
        self.root.mainloop()
    
    def _on_closing(self):
        """应用程序关闭时的处理"""
        self.root.destroy()


if __name__ == '__main__':
    try:
        app = MainApplication()
        app.run()
    except Exception as e:
        logger.error(f"应用程序启动失败: {e}")
        messagebox.showerror("错误", f"应用程序启动失败：{str(e)}")